#ifndef __M_SERVER_H__
#define __M_SERVER_H__
#include "online.hpp"
#include "matcher.hpp"
#include "util.hpp"
#include "session.hpp"
#include "db.hpp"
#include "room.hpp"

#define WWWROOT "./wwwroot/"
class gobang_server
{
private:
    std::string _web_root;     // 静态数据默认目录
    websocket_server _wssrv;   // 服务器套接字管理句柄
    user_table _uTable;        // 用户信息管理模块
    online_manager _oManager;  // 线上用户管理模块
    room_manager _rManager;    // 游戏房间管理模块
    session_manager _sManager; // 会话管理模块
    matcher _matcher;          // 匹配模块

private: // 工具
    // 用于http响应
    void http_resp(const websocket_server::connection_ptr &connPtr,
                   bool result, websocketpp::http::status_code::value statusCode, const std::string reason)
    {
        Json::Value respJson; // 响应json
        respJson["result"] = result;
        respJson["reason"] = reason;
        std::string respBody;
        json_util::serialize(respJson, respBody);
        connPtr->set_body(respBody);
        connPtr->set_status(statusCode);
        connPtr->append_header("Content-Type", "application/json");
        return;
    }

    // 用于websocket的响应
    void ws_resp(const websocket_server::connection_ptr &connPtr, Json::Value &respJson)
    {
        std::string body;
        
        json_util::serialize(respJson, body);
        DLOG("respJson:%s",body.c_str());
        connPtr->send(body);
    }

    // 从cookie，获取Key对应的value
    bool get_cookie_val(const std::string src, const std::string &key, std::string &value)
    {
        std::vector<std::string> cookie_arr;
        std::string sep = "; ";
        string_util::split(src, sep, cookie_arr);
        for (auto str : cookie_arr)
        {
            std::vector<std::string> ret;
            string_util::split(str, "=", ret);
            if (ret.size() != 2)
                continue;
            if (ret[0] == key)
            {
                value = ret[1];
                return true;
            }
        }
        return false;
    }

    // 通过请求的cookie获取sessionPtr(注：只用于websocket通信，且内部如果有错会响应)
    session_ptr get_sessionPtr_by_cookie(websocket_server::connection_ptr &connPtr,const std::string &optype)
    {
        // 1.登录验证:会话存在
        std::string cookieStr = connPtr->get_request_header("Cookie");
        Json::Value err_resp;
        if (cookieStr.empty())
        {
            // 头部cookie为空
            err_resp["optype"] = optype;
            err_resp["result"] = false;
            err_resp["reason"] = "请求无法获取cookie";
            DLOG("optype:%s,请求无法获取cookie",optype);
            ws_resp(connPtr, err_resp);
            return session_ptr();
        }
        std::string ssidStr;
        get_cookie_val(cookieStr, "SSID", ssidStr);
        if (ssidStr.empty())
        {
            // 无法获取cookie中的ssid
            err_resp["optype"] = optype;
            err_resp["result"] = false;
            err_resp["reason"] = "无法获取cookie中ssid，请重新登录";
            DLOG("optype:%s,无法获取cookie中ssid",optype);
            ws_resp(connPtr, err_resp);
            return session_ptr();
        }
        uint64_t ssid = std::stol(ssidStr);
        session_ptr ssionPtr = _sManager.get_session_by_ssid(ssid);
        if (ssionPtr.get() == nullptr)
        {
            // 用户ID出错，找不到
            err_resp["optype"] = optype;
            err_resp["result"] = false;
            err_resp["reason"] = "会话已过期，请重新登录";
            DLOG("optype:%s,会话已过期，请重新登录",optype);
            ws_resp(connPtr, err_resp);
            return session_ptr();
        }
        return ssionPtr;
    }

private: // 具体http请求事件处理
    // 静态资源请求处理
    void file_handler(websocket_server::connection_ptr &connPtr)
    {
        // 1、获取请求uir的资源路径
        websocketpp::http::parser::request req = connPtr->get_request();
        std::string uri = req.get_uri();
        // 2、组合文件的资源路径     相对根目录 + uri
        std::string realPath = _web_root + uri;

        // 3、请求如果是根目录，添加后缀（默认注册页面）
        if (realPath.back() == '/')
        {
            realPath += "login.html";
        }
        // 4、获取文件内容
        std::string body;
        bool ret = file_util::read(realPath, body);

        if (ret == false)
        {
            body += "<html>";
            body += "<head>";
            body += "<meta charset= 'UTF-8'/>";
            body += "</head>";
            body += "<body>";
            body += "<h1> Not Found </h1>";
            body += "</body>";
            connPtr->set_status(websocketpp::http::status_code::not_found);
            connPtr->set_body(body);
            return;
        }
        // 5、设置响应正文(正常情况，状态码会自动设置为200)
        connPtr->set_status(websocketpp::http::status_code::ok);
        connPtr->set_body(body);
    }

    // 用户注册请求处理
    void reg(websocket_server::connection_ptr &connPtr)
    {
        // 获取请求正文
        websocketpp::http::parser::request req = connPtr->get_request();
        std::string reqBody = connPtr->get_request_body();

        // 反序列化
        Json::Value user_info; // 请求json(注册信息)
        bool ret = json_util::unserialize(reqBody, user_info);
        if (ret == false)
        {
            DLOG("注册请求格式错误");
            http_resp(connPtr, false, websocketpp::http::status_code::bad_request, "请求格式错误");
            return;
        }
        // 数据库操作
        if (user_info["username"].isNull() || user_info["password"].isNull())
        {
            DLOG("请输入用户名/密码");
            http_resp(connPtr, false, websocketpp::http::status_code::bad_request, "请输入用户名/密码");
            return;
        }
        // 添加用户信息
        ret = _uTable.insert(user_info);
        if (ret == false)
        {
            DLOG("该用户名已占用");
            http_resp(connPtr, false, websocketpp::http::status_code::bad_request, "该用户名已占用");
            return;
        }
        // 成功注册

        http_resp(connPtr, true, websocketpp::http::status_code::ok, "用户名注册成功");
        return;
    }

    // 用户登录请求处理
    void login(websocket_server::connection_ptr &connPtr)
    {
        // 获取请求正文
        websocketpp::http::parser::request req = connPtr->get_request();
        std::string reqBody = connPtr->get_request_body();
        // 反序列化
        Json::Value user_info;
        bool ret = json_util::unserialize(reqBody, user_info);
        if (ret == false)
        {
            DLOG("登录请求格式错误");
            http_resp(connPtr, false, websocketpp::http::status_code::bad_request, "登录请求格式错误");
            return;
        }
        if (user_info["username"].isNull() || user_info["password"].isNull())
        {
            DLOG("登录用户名/密码不完整");
            http_resp(connPtr, false, websocketpp::http::status_code::bad_request, "请输入用户名/密码");
            return;
        }
        // 登录验证
        ret = _uTable.login(user_info);
        if (ret == false)
        {
            DLOG("用户名/密码错误");
            http_resp(connPtr, false, websocketpp::http::status_code::bad_request, "用户名/密码错误");
            return;
        }
        // 验证成功，创建会话
        uint64_t uid = user_info["id"].asUInt64();
        session_ptr newSessionPtr = _sManager.create_session(uid, LOGIN);
        if (newSessionPtr == session_ptr())
        {
            DLOG("创建会话失败");
            http_resp(connPtr, false, websocketpp::http::status_code::internal_server_error, "创建会话失败");
            return;
        }

        // 创建成功，设置会话生命周期
        uint64_t ssid = newSessionPtr->get_ssid();
        _sManager.set_session_expire_time(ssid, SESSION_TIMEOUT);
        // 设置响应头部
        std::string cookie_ssid = "SSID=" + std::to_string(ssid);
        connPtr->append_header("Set-Cookie", cookie_ssid);
        http_resp(connPtr, true, websocketpp::http::status_code::ok, "登录成功");
        return;
    }

    // 获取用户信息请求处理
    void info(websocket_server::connection_ptr &connPtr)
    {
        // 1、通过connPtr,获取请求中的cookie中的ssid
        std::string cookieStr = connPtr->get_request_header("Cookie");
        // DLOG("cookieStr:%s", cookieStr.c_str());
        if (cookieStr.empty())
        {
            // 头部cookie为空 -> 请求格式有问题
            http_resp(connPtr, false, websocketpp::http::status_code::bad_request, "请求无法获取cookie,请重新登录/请求");
            return;
        }

        std::string ssidStr;
        get_cookie_val(cookieStr, "SSID", ssidStr);
        // DLOG("ssidStr:%s", ssidStr.c_str());
        if (ssidStr.empty())
        {
            http_resp(connPtr, false, websocketpp::http::status_code::bad_request, "无法获取cookie中会话id,请重新登录");
            return;
        }

        // 2、 根据ssid，获取session_ptr
        uint64_t ssid = std::stol(ssidStr);
        // DLOG("ssid:%lu", ssid);
        session_ptr ssionPtr = _sManager.get_session_by_ssid(ssid);
        if (ssionPtr.get() == nullptr)
        {
            // 未查到ssid对应的会话存在 -> 会话已过期/会话未生成
            // 告诉用户需重新登录 -> 生成新会话
            http_resp(connPtr, false, websocketpp::http::status_code::bad_request, "会话已过期，请重新登录");
            return;
        }

        // 3、 根据session_ptr，获取uid
        uint64_t uid = ssionPtr->get_user_id();
        // 4、 根据uid，通过数据库，获取JSON形式的用户信息
        Json::Value user_info;
        bool ret = _uTable.select_by_id(uid, user_info);
        // DLOG("uid:%lu", uid);
        if (ret == false)
        {
            // 用户ID出错，找不到
            http_resp(connPtr, false, websocketpp::http::status_code::bad_request, "无法查询到对应用户ID");
            return;
        }

        // 5、更新会话生命周期 + 组织响应

        std::string body;
        json_util::serialize(user_info, body);
        connPtr->set_body(body);
        connPtr->append_header("Content-Type", "application/json");
        connPtr->set_status(websocketpp::http::status_code::ok);
        _sManager.set_session_expire_time(ssid, SESSION_TIMEOUT);
    }

private: // 具体的websocket请求处理
    // 房间：长链接链接成功（进入房间响应）
    // 注：当用户匹配成功后，从大厅跳转到房间时，跳转前大厅关闭时，会将用户移除大厅在线用户表，这是跳转完毕后调用的函数
    void wsopen_game_room(websocket_server::connection_ptr &connPtr)
    {
        // 1.获取ssionPtr、uid
        session_ptr ssionPtr = get_sessionPtr_by_cookie(connPtr,"open_room");
        Json::Value resp_json;
        uint64_t uid = ssionPtr->get_user_id();

        // 2.用户是否处于其它的大厅或房间
        if (_oManager.is_in_game_hall(uid) || _oManager.is_in_game_room(uid))
        {
            resp_json["optype"] = "room_ready";
            resp_json["result"] = false;
            resp_json["reason"] = "用户重复登录";
            ws_resp(connPtr, resp_json);
            return;
        }

        // 3.对应房间是否存在
        room_ptr roomPtr = _rManager.get_roomPtr_by_uid(uid);
        if (roomPtr.get() == nullptr)
        {
            // 房间不存在
            resp_json["optype"] = "room_ready";
            resp_json["result"] = false;
            resp_json["reason"] = "房间未创建";
            ws_resp(connPtr, resp_json);
            return;
        }
        // 4.将该用户添加进房间在线用户表
        _oManager.enter_game_room(uid, connPtr);

        // 5.将该用户会话生命周期设为永久
        _sManager.set_session_expire_time(ssionPtr->get_ssid(), SESSION_FOREVER);

        // 6.响应
        resp_json["optype"] = "room_ready";
        resp_json["winner"] = 0;
        resp_json["result"] = true;
        resp_json["uid"] = (Json::UInt64)uid;
        resp_json["room_id"] = (Json::UInt64)roomPtr->getRoomID();
        resp_json["white_id"] = (Json::UInt64)roomPtr->get_white_player_id();
        resp_json["black_id"] = (Json::UInt64)roomPtr->get_black_player_id();

        return ws_resp(connPtr, resp_json);
    }

    // 房间：长链接断开（退出房间响应）
    void wsclose_game_room(websocket_server::connection_ptr &connPtr)
    {
        // 获取ssionPtr
        session_ptr ssionPtr = get_sessionPtr_by_cookie(connPtr,"close_room");
        uint64_t uid = ssionPtr->get_user_id();
        if(ssionPtr.get() == nullptr)
        {
            return ;
        }
        // 将用户移除总的线上管理房间名单中
        _oManager.exit_game_room(uid);
        // 设置会话生命周期
        _sManager.set_session_expire_time(ssionPtr->get_ssid(), SESSION_TIMEOUT);
        //将该用户移出该房间
        _rManager.rmove_room_user(uid);
    }

    // 房间：长连接下消息处理（下棋、聊天）
    void wsmsg_game_room(websocket_server::connection_ptr &connPtr, websocket_server::message_ptr msg)
    {
        Json::Value respJson;
        std::string respBody;
        // 获取会话
        session_ptr sessionPtr = get_sessionPtr_by_cookie(connPtr,"msg_room");
        if (sessionPtr.get() == nullptr)
        {
            DLOG("房间-没有找到会话信息");
            return;
        }
        //房间信息        
        room_ptr roomPtr = _rManager.get_roomPtr_by_uid(sessionPtr->get_user_id());
        if(roomPtr.get() == nullptr)
        {
            // 无法将请求反序列化
            respJson["optype"] = "msg_room:unknow";
            respJson["result"] = false;
            respJson["reason"] = "无法查询到房间信息";
            ws_resp(connPtr, respJson);
        }

        // 获取msg内容
        Json::Value reqJson;
        std::string reqBody = msg->get_payload();
        bool ret = json_util::unserialize(reqBody, reqJson);
        if (ret == false)
        {
            // 无法将请求反序列化
            respJson["optype"] = "msg_room:unknow";
            respJson["result"] = false;
            respJson["reason"] = "无法分析请求信息";
            ws_resp(connPtr, respJson);
        }
        DLOG("房间：收到房间请求，开始处理....");
        //房间类自定义的请求响应    
        return roomPtr->handle_request(reqJson);
    }

    // 大厅：长链接链接（进入大厅响应）
    void wsopen_game_hall(websocket_server::connection_ptr &connPtr)
    {
        // 1.获取ssionPtr
        session_ptr ssionPtr = get_sessionPtr_by_cookie(connPtr,"open_hall");
        Json::Value resp_json;

        // 2.用户未登录（禁止重复登录）
        if (_oManager.is_in_game_hall(ssionPtr->get_user_id()) || _oManager.is_in_game_room(ssionPtr->get_user_id()))
        {
            resp_json["optype"] = "hall_ready";
            resp_json["result"] = false;
            resp_json["reason"] = "用户重复登录";
            ws_resp(connPtr, resp_json);
            return;
        }
        // 将用户纳入游戏大厅
        _oManager.enter_game_hall(ssionPtr->get_user_id(), connPtr);

        // 登录成功 ->响应给客户端
        resp_json["optype"] = "hall_ready";
        resp_json["result"] = true;
        ws_resp(connPtr, resp_json);
        // 3.将会话生命周期设为永久
        _sManager.set_session_expire_time(ssionPtr->get_ssid(), SESSION_FOREVER);
        return;
    }

    // 大厅：长链接断开（退出大厅/退出大厅进入房间）
    void wsclose_game_hall(websocket_server::connection_ptr &connPtr)
    {
        // 获取ssionPtr
        session_ptr ssionPtr = get_sessionPtr_by_cookie(connPtr,"close_hall");

        // 将用户移除游戏大厅
        _oManager.exit_game_hall(ssionPtr->get_user_id());
        // 设置会话生命周期
        _sManager.set_session_expire_time(ssionPtr->get_ssid(), SESSION_TIMEOUT);
    }

    // 大厅：长连接下消息处理（请求匹配、停止匹配）
    void wsmsg_game_hall(websocket_server::connection_ptr &connPtr, websocket_server::message_ptr msg)
    {
        Json::Value respJson;
        std::string respBody;
        // 获取会话
        session_ptr sessionPtr = get_sessionPtr_by_cookie(connPtr,"msg_hall");
        if (sessionPtr.get() == nullptr)
            return;
        // 获取msg内容
        std::string reqBody = msg->get_payload();
        Json::Value reqJson;
        bool ret = json_util::unserialize(reqBody, reqJson);
        if (ret == false)
        {
            // 无法将请求反序列化
            respJson["optype"] = "unknow";
            respJson["result"] = false;
            respJson["reason"] = "无法分析请求信息";
            ws_resp(connPtr, respJson);
        }

        // 处理分析完毕的请求json
        if (!reqJson["optype"].isNull() && reqJson["optype"].asString() == "match_start")
        {
            // 请求匹配
            _matcher.add(sessionPtr->get_user_id());
            respJson["optype"] = "match_start";
            respJson["result"] = true;
            ws_resp(connPtr, respJson);
        }
        else if (!reqJson["optype"].isNull() && reqJson["optype"].asString() == "match_stop")
        {
            // 停止匹配
            _matcher.remove(sessionPtr->get_user_id());
            respJson["optype"] = "match_stop";
            respJson["result"] = true;
            ws_resp(connPtr, respJson);
        }
        else
        {
            // 其它行为未知
            respJson["optype"] = "unknow";
            respJson["result"] = false;
            respJson["reason"] = "未知的请求";
            ws_resp(connPtr, respJson);
        }
    }

private: // 事件触发后，总的回调函数
    // 处理http请求
    void http_callback(websocketpp::connection_hdl hdl)
    {
        // 获取链接、请求等信息
        websocket_server::connection_ptr connPtr = _wssrv.get_con_from_hdl(hdl);
        websocketpp::http::parser::request req = connPtr->get_request();
        std::string uri = req.get_uri();
        std::string method = req.get_method();
        //
        if (method == "POST" && uri == "/reg")
        {
            // 注册请求
            return reg(connPtr);
        }
        else if (method == "POST" && uri == "/login")
        {
            // 登录请求
            return login(connPtr);
        }
        else if (method == "GET" && uri == "/info")
        {
            // 获取用户信息请求
            return info(connPtr);
        }
        else
        {
            // 获取静态资源(页面)请求
            return file_handler(connPtr);
        }
    }

    // 长连接（websocket）链接断开
    void wsclose_callback(websocketpp::connection_hdl hdl)
    {
        // 获取链接与请求
        websocket_server::connection_ptr connPtr = _wssrv.get_con_from_hdl(hdl);
        websocketpp::http::parser::request req = connPtr->get_request();
        std::string uri = req.get_uri();
        if (uri == "/hall")
        {
            // 游戏大厅长链接（开始匹配）
            return wsclose_game_hall(connPtr);
        }
        else if (uri == "/room")
        {
            // 游戏房间长链接（开始游戏）
            return wsclose_game_room(connPtr);
        }
    }

    // 长连接（websocket）连接成功
    void wsopen_callback(websocketpp::connection_hdl hdl)
    {
        // 获取链接与请求
        websocket_server::connection_ptr connPtr = _wssrv.get_con_from_hdl(hdl);
        websocketpp::http::parser::request req = connPtr->get_request();
        std::string uri = req.get_uri();
        if (uri == "/hall")
        {
            // 游戏大厅长链接（开始匹配）
            return wsopen_game_hall(connPtr);
        }
        else if (uri == "/room")
        {
            // 游戏房间长链接（开始游戏）
            return wsopen_game_room(connPtr);
        }
    }

    // 接收到客户端消息
    void wsmsg_callback(websocketpp::connection_hdl hdl, websocket_server::message_ptr msg)
    {
        // websocket⻓连接通信处理
        websocket_server::connection_ptr connPtr = _wssrv.get_con_from_hdl(hdl);
        websocketpp::http::parser::request req = connPtr->get_request();
        std::string uri = req.get_uri();
        if (uri == "/hall")
        {
            // 建⽴了游戏⼤厅的⻓连接
            return wsmsg_game_hall(connPtr, msg);
        }
        else if (uri == "/room")
        {
            // 建⽴了游戏房间的⻓连接
            DLOG("房间请求");
            return wsmsg_game_room(connPtr, msg);
        }
    }

public: // 默认函数
    gobang_server(const std::string &host,
                  const std::string &username,
                  const std::string &password,
                  const std::string &dbname,
                  uint16_t port = 3306,
                  const std::string wwwRoot = WWWROOT) : _web_root(wwwRoot),
                                                         _uTable(host, username, password, dbname, port),
                                                         _rManager(&_uTable, &_oManager),
                                                         _sManager(&_wssrv),
                                                         _matcher(&_rManager, &_uTable, &_oManager)
    {
        // 设置日志等级(不打印)
        _wssrv.set_access_channels(websocketpp::log::alevel::none);
        // 初始化asio调度器
        _wssrv.init_asio();
        // 地址重用
        _wssrv.set_reuse_addr(true);
        // 设置对应时间的回调函数
        _wssrv.set_http_handler(std::bind(&gobang_server::http_callback, this, std::placeholders::_1));
        _wssrv.set_close_handler(std::bind(&gobang_server::wsclose_callback, this, std::placeholders::_1));
        _wssrv.set_open_handler(std::bind(&gobang_server::wsopen_callback, this, std::placeholders::_1));
        _wssrv.set_message_handler(std::bind(&gobang_server::wsmsg_callback, this, std::placeholders::_1, std::placeholders::_2));
    }

public: // API
    void start(int port)
    {
        _wssrv.listen(port);
        // 6、开始获取新链接
        _wssrv.start_accept();
        // 7、启动服务器
        _wssrv.run();
    }
};
#endif